''' 
[viodef parser]
read a set of .viodef file, analyse the dependence relationship of classes and enums.
and make them class and enum of C++ into .h file.
'''

import sys  # for read argv
import copy
from enum import Enum

# unit type
class UnitType(Enum):
    OTHER = 'other'
    CLASS = 'class'
    ENUM = 'enum'

class DataType(Enum):
    __STD_TYPES__ = [
        'i8', 'i16', 'i32', 'i64',
        'u8', 'u16', 'u32', 'u64',
        'f32', 'f64', 'str',
        'i8[]', 'i16[]', 'i32[]', 'i64[]',
        'u8[]', 'u16[]', 'u32[]', 'u64[]',
        'f32[]', 'f64[]', 'str[]',
        ]
    STD_TYPE = 'std_type'
    USR_TYPE = 'usr_type'


class Line:
    def __init__(self, line: str) -> None:
        self.line:str = line

    def trim(self, command: str, symbol: str):
        self.line = copy.copy(self.line)
        if command == '' or command == 'all':
            return Line(self.line.replace(symbol, ''))
        elif command == 'left':
            return Line(self.line.lstrip(symbol))
        elif command == 'right':
            return Line(self.line.rstrip(symbol))
        elif command == 'before_first':
            last_idx = self.line.find(symbol)
            return Line(self.line[last_idx:] if last_idx != -1 else self.line)
        elif command == 'before_first_and_it':
            last_idx = self.line.find(symbol)
            return Line(self.line[last_idx + len(symbol) :] if last_idx != -1 else self.line)
        elif command == 'before_last':
            last_idx = self.line.rfind(symbol)
            return Line(self.line[last_idx:] if last_idx != -1 else self.line)
        elif command == 'before_last_and_it':
            last_idx = self.line.rfind(symbol)
            return Line(self.line[last_idx + len(symbol) :] if last_idx != -1 else self.line)
        elif command == 'after_first':
            last_idx = self.line.find(symbol)
            return Line(self.line[: last_idx + len(symbol)] if last_idx != -1 else self.line)
        elif command == 'after_first_and_it':
            last_idx = self.line.find(symbol)
            return Line(self.line[:last_idx] if last_idx != -1 else self.line)
        elif command == 'after_last':
            last_idx = self.line.rfind(symbol)
            return Line(self.line[: last_idx + len(symbol)] if last_idx != -1 else self.line)
        elif command == 'after_last_and_it':
            last_idx = self.line.rfind(symbol)
            return Line(self.line[:last_idx] if last_idx != -1 else self.line)
        else:
            print(
                'command must be one of: \n',
                '\tall: trim all symbol match\n',
                '\tleft: trim all symbol left match\n',
                '\tright: trim all symbol right match\n',
                '\tbefore_first: trim all char before the first symbol\n',
                '\tbefore_first_and_it: trim all char before the first symbol and the symbol\n',
                '\tbefore_last: trim all char before the last symbol\n',
                '\tbefore_last_and_it: trim all char before the last symbol and the symbol\n',
                '\tafter_first: trim all char after the first symbol\n',
                '\tafter_first_and_it: trim all char after the first symbol and the symbol\n',
                '\tafter_last: trim all char after the last symbol\n',
                '\tafter_last_and_it: trim all char after the last symbol and the symbol\n',
            )

class EnumUnit:
    def __init__(self) -> None:
        self.module: str = ''
        self.type: int = UnitType.ENUM
        self.name: str = ''
        self.var : list[tuple[str, str]] = []
        EnumUnit.name_index = {}
    
    def update_name_index(self):
        EnumUnit.name_index.update({self.name: self})
    
    @classmethod
    def find_by_name(cls, name):
        return EnumUnit.name_index[name]

class ClassUnit:
    def __init__(self) -> None:
        self.module: str = ''
        self.type: int = UnitType.CLASS
        self.name: str = ''
        self.depends: list[ClassUnit] = []
        self.var : list[tuple[str, str, str, int]] = []
        ClassUnit.name_index = {}
    
    def update_name_index(self):
        ClassUnit.name_index.update({self.name: self})
    
    @classmethod
    def find_by_name(cls, name):
        return ClassUnit.name_index[name]


class Parser:
    def __init__(self) -> None:
        self.modules: dict[str, list[str]] = {}
        self.classes: list[ClassUnit] = []
        self.enums: list[EnumUnit] = []

    def parse_file(self, file_name: str):
        with open(file_name, 'r') as file:
            print('---')
            print("parse file: ", file_name)
            print()
            last_line_is_class_or_enum = UnitType.OTHER
            last_class_enum_name = ''
            last_line_idx = 0
            cur_line_idx = 0
            module_name = Line(file_name).trim('before_last_and_it', '/').trim('after_first_and_it', '.').line
            for line in file:
                cur_line_idx += 1
                line_ins = Line(line).trim('right', '\n')
                line_left_space_trimed = line_ins.trim('left', ' ')
                print(cur_line_idx, line_left_space_trimed.line)
                if len(line_left_space_trimed.line) == 0:
                    print('^ jump empty line\n')
                    continue
                elif line_left_space_trimed.line.startswith('with'):
                    self.parse_with_line(line_ins.line, module_name)
                elif line_left_space_trimed.line.startswith('['):
                    line_start_with_class_enum = line_left_space_trimed.trim('left', '[').trim('left', ' ')
                    if line_start_with_class_enum.line.startswith('class'):
                        last_line_is_class_or_enum = UnitType.CLASS
                        last_line_idx = cur_line_idx
                        last_class_enum_name = self.parse_class_name_line(line_ins.line, module_name)
                    elif line_start_with_class_enum.line.startswith('enum'):
                        last_line_is_class_or_enum = UnitType.ENUM
                        last_line_idx = cur_line_idx
                        last_class_enum_name = self.parse_enum_name_line(line_ins.line, module_name)
                    else:
                        print('section line must start with [class or [enum')
                elif line_left_space_trimed.line.startswith('#'):
                    print('^ jump the comment line\n')
                else:
                    if last_line_is_class_or_enum == UnitType.CLASS:
                        self.parse_class_member_line(line_ins.line, last_class_enum_name, module_name)
                    elif last_line_is_class_or_enum == UnitType.ENUM:
                        self.parse_enum_member_line(line_ins.line, last_class_enum_name, module_name)
                    else:
                        print(
                            cur_line_idx,
                            ':',
                            'this line is not suppose to apprea here!, class or enum not found!, last line was: ',
                            last_line_idx,
                        )

    def parse_with_line(self, line: str, module_name: str):
        print('^ parse with line')
        with_name = Line(line).trim('before_first_and_it', 'with').trim('left', ' ').trim('after_first_and_it', '#').trim('right', ' ').line
        with_elements = [Line(ele).trim('right', ',').line for ele in with_name.split(' ')]
        self.modules.update({module_name: [*with_elements]})
        print('> with:',*with_elements)
        print()

    def parse_class_name_line(self, line: str, module_name: str):
        print('^ parse class name line')
        class_name = Line(line).trim('before_first_and_it', 'class').trim('left', ' ').trim('after_first_and_it', ']').trim('right', ' ').line
        unit = ClassUnit()
        unit.module = module_name
        unit.type = UnitType.CLASS
        unit.name = class_name
        unit.update_name_index()
        self.classes.append(unit)
        print('> class:',class_name)
        print()
        return class_name

    def parse_class_member_line(self, line: str, class_name: str, module_name: str):
        print('^ parse class member line of class: [' + class_name + ']')
        print('> class:', class_name)
        var_name = Line(line).trim('after_first_and_it', ':').trim('right', ' ').line
        type_name = Line(line).trim('before_first_and_it', ':').trim('left', ' ').trim('after_first_and_it', '=').trim('right', ' ').line
        init_value_expression = Line(line).trim('before_first_and_it', '=').trim('left', ' ').trim('after_first_and_it', '#').trim('right', ' ').line
        comment = '' if line.find('#') == -1 else Line(line).trim('before_first_and_it', '#').trim('left', ' ').line
        ClassUnit.find_by_name(class_name).var.append((var_name, type_name, init_value_expression, DataType.STD_TYPE if type_name in DataType.__STD_TYPES__ else DataType.USR_TYPE))
        if type_name not in DataType.__STD_TYPES__:
            ClassUnit.find_by_name(class_name).depends.append(type_name)
        print('> var: '+var_name+', type: '+type_name+', init: '+init_value_expression+', comment: '+comment)
        print()

    def parse_enum_name_line(self, line: str, module_name: str):
        print('^ parse enum name line\n')
        enum_name = Line(line).trim('before_first_and_it', 'enum').trim('left', ' ').trim('after_first_and_it', ']').trim('right', ' ').line
        unit = EnumUnit()
        unit.module = module_name
        unit.type = UnitType.ENUM
        unit.name = enum_name
        unit.update_name_index()
        self.enums.append(unit)
        print('> enum:',enum_name)
        print()
        return enum_name

    def parse_enum_member_line(self, line: str, enum_name: str, module_name: str):
        print('^ parse enum member line of enum name: [' + enum_name + ']')
        print('> enum:', enum_name)
        var_name = Line(line).trim('after_first_and_it', '=').trim('right', ' ').line
        init_value_expression = Line(line).trim('before_first_and_it', '=').trim('left', ' ').trim('after_first_and_it', '#').trim('right', ' ').line
        comment = '' if line.find('#') == -1 else Line(line).trim('before_first_and_it', '#').trim('left', ' ').line
        EnumUnit.find_by_name(enum_name).var.append((var_name, init_value_expression))
        print('> var: '+var_name+', init: '+init_value_expression+', comment: '+comment)
        print()

    def show(self):
        print('modules: ')
        for key, value in self.modules.items():
            print('\t'+key+':',*value)
            print("\t-")
        print()
        print('classes:')
        for clas in self.classes:
            print('\tname:', clas.name, '\n',
                  '\tmodule:', clas.module, '\n',
                  '\ttype:', clas.type, '\n',
                  '\tdepends:', clas.depends,'\n',
                  '\tvar:', clas.var)
            print("\t-")
        print()
        print('enums:')
        for enum in self.enums:
            print('\tname:', enum.name, '\n',
                  '\tmodule:', enum.module, '\n',
                  '\ttype:', enum.type, '\n',
                  '\tvar:', enum.var)
            print("\t-")


if __name__ == '__main__':
    if len(sys.argv) < 2:
        print('need at least ONE argument: father folder name of .viodef files')
        sys.exit(1)

    parser = Parser()
    for file_name in sys.argv[1:]:
        parser.parse_file(file_name)
    parser.show()

    pass
